using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace ReportMaintenanceUtility
{
	public class ReportWrapper : GenericBase<ReportWrapper>
	{
		#region PRIVATE MEMBERS
		private string							_name		= string.Empty;
		private string							_path		= string.Empty;
		private Dictionary<string, Parameter>	_parameters = null;				//TODO: create new class derived from CustomList<Parameter>
		private Dictionary<string, DataSet>		_dataSets	= null;
		private Report							_seed		= null;
		private List<ChangeLog> _log = null;
		#endregion

		#region PUBLIC PROPERTIES
		public string Name
		{
			get { return this._name; }
		}
		public string Path
		{
			get { return this._path; }
		}
		public Dictionary<string, Parameter> Parameters
		{
			get { return this._parameters; }
		}
		public Dictionary<string, DataSet> DataSets
		{
			get { return this._dataSets; }
		}
		public bool ReadOnly
		{
			get { return new FileInfo(this._path).IsReadOnly; }
		}
		public List<ChangeLog> Log
		{
			get { return this._log; }
		}
		#endregion

		#region CONSTRUCTOR(S)
		protected ReportWrapper(ReportWrapper rw)
		{
			if (rw == null)
			{
				throw new ArgumentNullException();
			}
			else
			{
				this._name = rw._name;
				this._path = rw._path;
				this._seed = (Report)rw.CreateSeedCopy();
				this.Initialize();
			}
		}
		public ReportWrapper(Report report, FileInfo fi)
		{
			if (report == null || fi == null)
			{
				throw new ArgumentNullException();
			}
			else
			{
				this._name = fi.Name;
				this._path = fi.FullName;
				this._seed = report;
				this.Initialize();
			}
		}
		protected override void Initialize()
		{
			this._log = new List<ChangeLog>();
			this._dataSets = new Dictionary<string, DataSet>();
			this._parameters = new Dictionary<string, Parameter>();

			if (this._seed.ItemsElementName != null && this._seed.ItemsElementName.Length > 0)
			{
				this.PopulateDataSetList();
				this.PopulateParameterList();
			}
		}
		#endregion

		#region PRIVATE METHODS
		private void PopulateParameterList()
		{
			List<ItemsChoiceType37> elements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
			try
			{
				int parmIdx = elements.IndexOf(ItemsChoiceType37.ReportParameters);
				if (parmIdx > -1)
				{
					Parameter p = null;
					ReportParametersType rpts = ((ReportParametersType)this._seed.Items[parmIdx]);
					if (rpts.ReportParameter != null && rpts.ReportParameter.Length > 0)
					{
						foreach (ReportParameterType pt in rpts.ReportParameter)
						{
							if (pt != null)
							{
								p = new Parameter(pt, this._dataSets);
								p.PropertyChanged += this.PropertyChangedListener;
								this._parameters.Add(p.Name, p);
							}
						}
					}
				}
			}
			catch (IndexOutOfRangeException ex)
			{
				Console.WriteLine(ex.Message);
			}
		}
		private void PopulateDataSetList()
		{
			List<ItemsChoiceType37> elements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
			try
			{
				int dsIdx = elements.IndexOf(ItemsChoiceType37.DataSets);
				if (dsIdx > -1)
				{
					DataSetType[] dsts = ((DataSetsType)this._seed.Items[dsIdx]).DataSet;
					if (dsts != null && dsts.Length > 0)
					{
						foreach (DataSetType dst in dsts)
						{
							if (!this._dataSets.ContainsKey(dst.Name))
							{
								this._dataSets.Add(dst.Name, new DataSet(dst));
							}
						}
					}
				}
			}
			catch (IndexOutOfRangeException ex)
			{
				Console.WriteLine(ex.Message);
			}
		}
		#endregion

		#region PUBLIC METHODS

		public void CreateParameterCopy(Parameter p)
		{
			this.CreateParameter(p.CreateDeepCopy());
		}
		private void CreateParameter(Parameter p)
		{
			this.PrepStateChange("Parameters");

			ReportParametersType rpts = null;
			List<ReportParameterType> parameters = null;

			List<ItemsChoiceType37> elements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
			List<object> items = new List<object>(this._seed.Items);

			//report parameters seed element...
			int idx = elements.IndexOf(ItemsChoiceType37.ReportParameters);
			if (idx > -1)
			{
				//get reference to existing element...
				rpts = (ReportParametersType)items[idx];
				if (rpts.ReportParameter != null && rpts.ReportParameter.Length > 0)
				{
					parameters = new List<ReportParameterType>(rpts.ReportParameter);
				}
				else
				{
					parameters = new List<ReportParameterType>();
				}
			}
			else
			{
				//create new parameters seed element...
				rpts = new ReportParametersType();

				this.AddSeedItem(elements, ItemsChoiceType37.ReportParameters, rpts);

				//create empty list to hold new parameter
				parameters = new List<ReportParameterType>();
			}

			//add new parameter seed object to list
			idx = parameters.FindIndex(delegate(ReportParameterType rpt) { return rpt.Name.Equals(p.Name); });
			if (idx > -1)
			{
				parameters[idx] = p.GetSeedObject();
			}
			else
			{
				parameters.Add(p.GetSeedObject());
			}

			//convert list back into array
			rpts.ReportParameter = parameters.ToArray();

			if (p.HasDataSet)
			{
				DataSet ds = this.CreateDataSetCopy(p.ValuesDataSet, p.Name);
				if (ds != null)
				{
					p.ValidValues.DataSetReference.Target = ds;
				}
				ds = this.CreateDataSetCopy(p.DefaultDataSet, p.Name);
				if (ds != null)
				{
					p.DefaultValue.ValidValues.DataSetReference.Target = ds;
				}
			}

			//reconfigure report specific items...
			p.ResetDataSetLibrary(this._dataSets);
			p.ClearListeners();
			p.PropertyChanged += this.PropertyChangedListener;

			if (this._parameters.ContainsKey(p.Name))
			{
				this._parameters[p.Name] = p;
			}
			else
			{
				this._parameters.Add(p.Name, p);
			}
		}
		private DataSet CreateDataSetCopy(DataSet ds, string parmName)
		{
			if (ds != null)
			{
				List<ItemsChoiceType37> elements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
				List<object> items = new List<object>(this._seed.Items);

				DataSetsType dsts = null;
				List<DataSetType> dsList = new List<DataSetType>();

				//first, determine if a DataSetsType seed element already exists or not...
				int idx = elements.IndexOf(ItemsChoiceType37.DataSets);
				if (idx == -1)
				{
					//if not, then create a new one using the given dataset...
					dsts = new DataSetsType();
					dsList.Add(ds.GetSeedObject());
					dsts.DataSet = dsList.ToArray();
					this.AddSeedItem(elements, ItemsChoiceType37.DataSets, dsts);
					this._dataSets.Add(ds.Name, ds);
					return null;
				}
				else
				{
					//root seed element exists, so get a reference to it...
					dsts = (DataSetsType)items[idx];
					if (dsts.DataSet != null)
					{
						//if the root DataSetsType element has child DataSet objects, add them to the list...
						dsList.AddRange(dsts.DataSet);
					}

					//if another dataset exists in the current report with the same name as the new dataset...
					if (this._dataSets.ContainsKey(ds.Name))
					{
						DataSet local = this._dataSets[ds.Name];

						//if they are the same object, do nothing...
						if (local == ds)
						{
							return null;
						}
						else if (CompareTo<DataSet>.Compare(local, ds))
						{
							//if they are equivalent (same values, but different object reference), then return the local reference...
							return local;
						}
						else
						{
							//else, a dataset with the same name exists in the current report that is different than the version being copied from...

							//determine if the local version is being used by any parameters...
							foreach (KeyValuePair<string, Parameter> entry in this._parameters)
							{
								if (entry.Value.HasDataSet)
								{
									if (entry.Value.ValuesDataSet == local || entry.Value.DefaultDataSet == local)
									{
										if (entry.Key == parmName)
										{
											break;
										}
										else
										{
											//the dataset is being used by a parameter other than the one being copied to...
											//TODO: add logic here to handle this 
											throw new Exception();
										}
									}
								}
							}

							//the local dataset version is NOT being used by other parameters, so we can replace it...
							idx = dsList.FindIndex(delegate(DataSetType dst) { return dst.Name.Equals(ds.Name); });
							if (idx == -1)
							{
								dsList.Add(ds.GetSeedObject());
							}
							else
							{
								//found matching dataset...
								dsList[idx] = ds.GetSeedObject();
							}

							dsts.DataSet = dsList.ToArray();
							this._dataSets[ds.Name] = ds;

							return null;
						}
					}
					else
					{
						//simply add seed and DataSet objects to report...
						dsList.Add(ds.GetSeedObject());
						dsts.DataSet = dsList.ToArray();
						this._dataSets.Add(ds.Name, ds);
					}
				}
			}
			return null;
		}
		public string CreateNewParameter()
		{
			//create new paramter seed object
			ReportParameterType pt = new ReportParameterType();

			//create a new, distinct name
			string newName = string.Empty;
			int i = 1;
			do
			{
				newName = string.Format("NewReportParameter{0}", i);
				if (!this.Parameters.ContainsKey(newName))
				{
					pt.Name = newName;
					break;
				}
				i++;
			} while (true);

			//add enums for default parameter settings
			List<ItemsChoiceType33> ptElements = new List<ItemsChoiceType33>();
			ptElements.Add(ItemsChoiceType33.DataType);
			ptElements.Add(ItemsChoiceType33.Prompt);

			//add values for default parameter settings
			List<object> ptItems = new List<object>();
			ptItems.Add(ReportParameterTypeDataType.String);
			ptItems.Add(pt.Name);

			//convert lists of default values / enums to arrays
			pt.ItemsElementName = ptElements.ToArray();
			pt.Items = ptItems.ToArray();

			//create new Paramter object from the new seed object...
			Parameter p = new Parameter(pt, this._dataSets);
			p.CreateValidValues();
			p.CreateDefaultValues();

			//add new parameter and its underlying seed objects to the current report...
			this.CreateParameter(p);

			//return the new parameter's name
			return p.Name;
		}
		public void DeleteParameter(string parameterName)
		{
			List<ItemsChoiceType37> reportElements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
			List<object> reportItems = new List<object>(this._seed.Items);
			List<ReportParameterType> parameters = null;

			ReportParametersType rpts = null;
			int idx = reportElements.IndexOf(ItemsChoiceType37.ReportParameters);

			if (idx > -1)
			{
				//get reference to existing element...
				rpts = (ReportParametersType)reportItems[idx];

				if (rpts != null && rpts.ReportParameter.Length > 0)
				{
					parameters = new List<ReportParameterType>(rpts.ReportParameter);
					ReportParameterType pt = parameters.Find(delegate(ReportParameterType rpt) { return rpt.Name.Equals(parameterName, StringComparison.CurrentCultureIgnoreCase); });
					if (pt != null)
					{
						parameters.Remove(pt);
					}
				}

				//if no parameters currently exist...
				if (parameters == null || parameters.Count == 0)
				{
					//then remove ReportParametersType from seed hierarchy...
					reportElements.RemoveAt(idx);
					reportItems.RemoveAt(idx);

					this._seed.ItemsElementName = reportElements.ToArray();
					this._seed.Items = reportItems.ToArray();
				}
				else
				{
					rpts.ReportParameter = parameters.ToArray();
				}
			}

			//remove parameter from dictionary, if exists...
			if (this.Parameters.ContainsKey(parameterName))
			{
				this.Parameters.Remove(parameterName);
			}
		}

		#endregion

		#region INTERFACE IMPLEMENTATION(S)
		public override bool EqualTo(ReportWrapper other)
		{
			if (other == null)
			{
				return false;
			}
			else
			{
				throw new NotImplementedException();
			}
		}
		public override List<ChangeLog> SaveState()
		{
			List<ChangeLog> newLog = new List<ChangeLog>();

			if (this._isDirty)
			{
				try
				{
					List<ItemsChoiceType37> elements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
					List<object> items = new List<object>(this._seed.Items);

					//Parameters
					int idx = elements.IndexOf(ItemsChoiceType37.ReportParameters);
					if (this._parameters.Count == 0 && idx > -1)
					{
						//remove element and item from arrays
						elements.RemoveAt(idx);
						items.RemoveAt(idx);

						this._seed.ItemsElementName = elements.ToArray();
						this._seed.Items = items.ToArray();
					}
					else if (this._parameters.Count > 0)
					{
						//add element and item to arrays
						ReportParametersType rpts = null;
						List<ReportParameterType> rptList = new List<ReportParameterType>();
						Dictionary<string, Parameter> newList = new Dictionary<string,Parameter>();

						foreach (KeyValuePair<string, Parameter> entry in this._parameters)
						{
							if (entry.Value.IsDirty)
							{
								newLog.AddRange(entry.Value.SaveState());
							}
							rptList.Add(entry.Value.GetSeedObject());
							newList.Add(entry.Value.Name, entry.Value);
						}
						this._parameters = newList;

						if (idx == -1)
						{
							rpts = new ReportParametersType();
							rpts.ReportParameter = rptList.ToArray();

							elements.Add(ItemsChoiceType37.ReportParameters);
							items.Add(rpts);

							this._seed.ItemsElementName = elements.ToArray();
							this._seed.Items = items.ToArray();
						}
						else
						{
							rpts = (ReportParametersType)this._seed.Items[idx];
							rpts.ReportParameter = rptList.ToArray();
						}
					}

					//DataSets
					idx = elements.IndexOf(ItemsChoiceType37.DataSets);
					if (this._dataSets.Count == 0 && idx > -1)
					{
						elements.RemoveAt(idx);
						items.RemoveAt(idx);

						this._seed.ItemsElementName = elements.ToArray();
						this._seed.Items = items.ToArray();
					}
					else if (this._dataSets.Count > 0)
					{
						//add element and item to arrays
						DataSetsType dsts = null;
						List<DataSetType> dsList = new List<DataSetType>();
						Dictionary<string, DataSet> newList = new Dictionary<string, DataSet>();

						foreach (KeyValuePair<string, DataSet> entry in this._dataSets)
						{
							if (entry.Value.IsDirty)
							{
								newLog.AddRange(entry.Value.SaveState());
							}
							dsList.Add(entry.Value.GetSeedObject());
							newList.Add(entry.Value.Name, entry.Value);
						}
						this._dataSets = newList;

						if (idx == -1)
						{
							dsts = new DataSetsType();
							dsts.DataSet = dsList.ToArray();

							elements.Add(ItemsChoiceType37.DataSets);
							items.Add(dsts);

							this._seed.ItemsElementName = elements.ToArray();
							this._seed.Items = items.ToArray();
						}
						else
						{
							dsts = (DataSetsType)this._seed.Items[idx];
							dsts.DataSet = dsList.ToArray();
						}
					}
				}
				catch (Exception ex)
				{
					//if error, display message and reload file?
					throw ex;
				}
			}

			this._log.AddRange(newLog);

			this.ClearState();

			return newLog;
		}
		public override void RestoreState()
		{
			if (this._copy != null)
			{
				this._name = this._copy._name;
				this._dataSets = this._copy._dataSets;
				this._parameters = this._copy._parameters;
				this._path = this._copy._path;
				this._seed = this._copy._seed;
				this.CopyListeners(this._copy);
				this._log.Clear();
			}
			this.ClearState();
		}
		public override ReportWrapper CreateDeepCopy()
		{
			if (this._copy != null)
			{
				return this._copy;
			}
			return new ReportWrapper(this);
		}
		public Report GetSeedObject()
		{
			return this._seed;
		}
		#endregion

		public override object CreateSeedCopy()
		{
			//create new root Report seed object...
			Report r = new Report();

			//IMPORTANT: this process is done this way to ensure that ALL items (not just datasets or parameters) are copied...
			List<ItemsChoiceType37> elements = new List<ItemsChoiceType37>(this._seed.ItemsElementName);
			List<object> items = new List<object>(this._seed.Items);

			r.ItemsElementName = elements.ToArray();
			r.Items = items.ToArray();

			//if DataSetsType objects exist...
			int idx = elements.IndexOf(ItemsChoiceType37.DataSets);
			if (idx > -1)
			{
				//replace the referenced object with a new, distinct copy...
				DataSetsType dsts = new DataSetsType();
				List<DataSetType> dstList = new List<DataSetType>();

				//create a new list of DataSetType seed objects from the current list of DataSets
				foreach (DataSet ds in this._dataSets.Values)
				{
					dstList.Add((DataSetType)ds.CreateSeedCopy());
				}

				//convert the list to an array...
				dsts.DataSet = dstList.ToArray();

				//overwrite the original reference with the new copy...
				r.Items[idx] = dsts;
			}

			//if the items array contains a ReportParametersType object...
			idx = elements.IndexOf(ItemsChoiceType37.ReportParameters);
			if (idx > -1)
			{
				//replace the referenced object with a new, distinct copy...
				ReportParametersType rpts = new ReportParametersType();
				List<ReportParameterType> rptList = new List<ReportParameterType>();

				//create a new list of ReportParameterType seed objects from the current list of Parameters
				foreach (Parameter p in this._parameters.Values)
				{
					rptList.Add((ReportParameterType)p.CreateSeedCopy());
				}

				//convert the list to an array...
				rpts.ReportParameter = rptList.ToArray();

				//overwrite the original reference with the new copy...
				r.Items[idx] = rpts;
			}

			//return the new root Report seed object...
			return r;
		}

		private void RemoveSeedItem(List<ItemsChoiceType37> elements, int idx)
		{
			if (idx > -1 && idx < elements.Count)
			{
				elements.RemoveAt(idx);

				List<object> items = new List<object>(this._seed.Items);
				items.RemoveAt(idx);

				this._seed.Items = items.ToArray();
				this._seed.ItemsElementName = elements.ToArray();
			}
		}

		private void AddSeedItem(List<ItemsChoiceType37> elements, ItemsChoiceType37 element, object value)
		{
			if (!elements.Contains(element))
			{
				elements.Add(element);

				List<object> items = new List<object>(this._seed.Items);
				items.Add(value);

				this._seed.ItemsElementName = elements.ToArray();
				this._seed.Items = items.ToArray();
			}
		}

	}
}
